import inspect

from re_common.baselibrary.utils.core.mdeprecated import try_except2_async

from apps.crawler_platform.core_callback.err_callback import all_client_back_callback
from apps.crawler_platform.core_platform.core_g import IsHasEnum
from apps.crawler_platform.core_platform.core_nosql import NoSqlUtil
from apps.crawler_platform.core_platform.downler import DownStep


class CoreConterollerMixin(DownStep):
    """
    核心控制器,控制整个流程的逻辑
    """

    # 私有方法不建议外部调用
    async def _base_task_step(self):
        """
        最简单的步骤控制器，会按照数据库配置顺序下载，如果中途任何一个下载失败，
        会返回错误。
        翻页逻辑由down()函数兼容,在翻页时失败不会马上return,而是尽可能的下载其他页
        控制器中keys 为分组与编号的组合，但本控制器不处理分组逻辑
        :return:
        """
        # 限制只允许指定函数调用 由于这里面没有save_sal 由easy_task_step保证save_sal的执行
        if inspect.stack()[1].function != "easy_task_step":
            raise Exception("不允许easy_task_step以外的函数调用_base_task_step")
        self.step_info = f"获取redis的配置信息，配置信息来源于sql表taskinfo"
        # 获取redis配置信息
        bools, err_info = await self.get_redis()
        if not bools:
            return bools, err_info
        self.step_info = f"获取sql的单条数据"
        bools, err_info = await self.get_sql()
        if not bools:
            return bools, err_info
        self.step_info = f"开始循环redis，下载html"
        # 设置对应的model来接收下载的结果
        self.pm.set_downmodel()
        # 对于一个网页可能有多个url需要下载，循环下载
        for keys in sorted(self.get_redis_model().parse_dict.keys()):
            # 设置pm的keys
            self.pm.keys = keys
            # 每个key默认失败
            self.pm.set_is_his(False)

            self.step_info = f"开始解析redis结构,key is {keys};"
            bools, err_info = self.para_redis(keys)
            if not bools:
                return bools, err_info
            # 有可能出现失败无法加1的情况 先加1做个保险
            self.step_info = f"设置单个key失败+1"
            await self.save_sql(type="set_failcount", code="f_key;f_last_key")
            if not bools:
                return bools, err_info
            # 下载重复3次,每次获取新的代理防止代理失效
            self.step_info = f"开始下载,key is {keys};"
            bools, err_info = await self.down()
            if not bools:
                # down_dict[keys] down_dict 这里使用哪种有待商榷
                err_info["html"] = str(self.pm.down_model.down_dict[keys])
                return bools, err_info
            self.step_info = f"验证网para_next页,key is {keys};"
            bools, err_info = self.verify_html()
            if not bools:
                return bools, err_info
        self.step_info = f"循环下载完成，开始保存nosql;"
        # 存入nosql
        bools, err_info = await self.save_nosql_html()
        if not bools:
            return bools, err_info
        self.step_info = f"循环下载完成，解析数据;"
        # 解析数据 包括解析上一步需要的sql信息和下一步的信息
        bools, err_info = await self.deal_html()
        if not bools:
            return bools, err_info
        self.step_info = f"验证解析出来的数据是否正确"
        bools, err_info = self.verify_para()
        if not bools:
            return bools, err_info
        # 返回一个整体的状态
        return bools, ""

    @try_except2_async(callback=all_client_back_callback, is_print=True)
    async def easy_task_step(self):
        """
        将save_sql 从base_task_step() 方法独立到easy_task_step中，目的是当base_task_step() 任意地方出现
        错误，也能保证save_sql打标逻辑的正常运行。
        base_task_step 只能被easy_task_step调用
        :return:
        """
        bools, err_info = await self._base_task_step()
        if bools:
            self.step_info = f"循环下载完成，更新sql;"
        # 保存下一级数据和状态打标
        bools1, err_info1 = await self.save_sql()
        if not bools1:
            self.step_info = f"循环下载完成1，更新sql;"
            return bools1, err_info1
        # 下载中途失败退出也需要更新状态码，如果存在的话
        if not bools:
            if err_info is not None and isinstance(err_info, dict):
                if err_info.get("code", 0) != 0:
                    bools2, err_info2 = await self.save_sql(type="save_state", code=err_info["code"])
                    if bools2:
                        self.step_info = f"循环下载完成，更新状态码;"

        if bools:
            self.step_info = f"循环下载完成，所有逻辑成功;"
        return bools, err_info

    @try_except2_async(callback=all_client_back_callback, is_print=True)
    async def page_task_step(self):
        """
        逻辑上来说翻页下载会有一些失败，但理论上下载完成一轮不管下载了多少都应该
        放入latest。但由于没下载完，为了能跳过应该copy一份放入new_data
        :return:
        """
        bools, err_info = await self.easy_task_step()
        # 设置nosql的newdata字段，防止下载全部成功,但解析不成功的情况
        # 一般不翻页不会做这个设置，直接重新全部下载保持逻辑简单
        if not bools and self.pm.get_is_his(IsHasEnum.LATEST):
            bools_reset, dicts = await NoSqlUtil.reset_new_data(self.pm)
            if not bools_reset:
                bools, err_info = bools_reset, dicts
        return bools, err_info
